package Win32::OleAccxs;

use 5.008;
use strict;
use warnings;

use Carp;

use Data::Dumper;

require Exporter;

our @ISA = qw(Exporter);

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.

# This allows declaration	use Win32::OleAccxs ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
    AccessibleObjectFromPoint	
    AccessibleObjectFromWindow

    GetRoleText

    OBJID_WINDOW        
    OBJID_SYSMENU       
    OBJID_TITLEBAR      
    OBJID_MENU          
    OBJID_CLIENT        
    OBJID_VSCROLL       
    OBJID_HSCROLL       
    OBJID_SIZEGRIP      
    OBJID_CARET         
    OBJID_CURSOR        
    OBJID_ALERT         
    OBJID_SOUND         
    OBJID_QUERYCLASSNAMEIDX 
    OBJID_NATIVEOM      

    SELFLAG_NONE            
    SELFLAG_TAKEFOCUS       
    SELFLAG_TAKESELECTION   
    SELFLAG_EXTENDSELECTION 
    SELFLAG_ADDSELECTION    
    SELFLAG_REMOVESELECTION 

    STATE_SYSTEM_UNAVAILABLE        
    STATE_SYSTEM_SELECTED           
    STATE_SYSTEM_FOCUSED            
    STATE_SYSTEM_PRESSED            
    STATE_SYSTEM_CHECKED            
    STATE_SYSTEM_MIXED              
    STATE_SYSTEM_INDETERMINATE      
    STATE_SYSTEM_READONLY           
    STATE_SYSTEM_HOTTRACKED         
    STATE_SYSTEM_DEFAULT            
    STATE_SYSTEM_EXPANDED           
    STATE_SYSTEM_COLLAPSED          
    STATE_SYSTEM_BUSY               
    STATE_SYSTEM_FLOATING           
    STATE_SYSTEM_MARQUEED           
    STATE_SYSTEM_ANIMATED           
    STATE_SYSTEM_INVISIBLE          
    STATE_SYSTEM_OFFSCREEN          
    STATE_SYSTEM_SIZEABLE           
    STATE_SYSTEM_MOVEABLE           
    STATE_SYSTEM_SELFVOICING        
    STATE_SYSTEM_FOCUSABLE          
    STATE_SYSTEM_SELECTABLE         
    STATE_SYSTEM_LINKED             
    STATE_SYSTEM_TRAVERSED          
    STATE_SYSTEM_MULTISELECTABLE    
    STATE_SYSTEM_EXTSELECTABLE      
    STATE_SYSTEM_ALERT_LOW          
    STATE_SYSTEM_ALERT_MEDIUM       
    STATE_SYSTEM_ALERT_HIGH         
    STATE_SYSTEM_PROTECTED          
    STATE_SYSTEM_VALID              

    ROLE_SYSTEM_ALERT               
    ROLE_SYSTEM_ANIMATION           
    ROLE_SYSTEM_APPLICATION         
    ROLE_SYSTEM_BORDER              
    ROLE_SYSTEM_BUTTONDROPDOWN      
    ROLE_SYSTEM_BUTTONDROPDOWNGRID  
    ROLE_SYSTEM_BUTTONMENU          
    ROLE_SYSTEM_CARET               
    ROLE_SYSTEM_CELL                
    ROLE_SYSTEM_CHARACTER           
    ROLE_SYSTEM_CHART               
    ROLE_SYSTEM_CHECKBUTTON         
    ROLE_SYSTEM_CLIENT              
    ROLE_SYSTEM_CLOCK               
    ROLE_SYSTEM_COLUMN              
    ROLE_SYSTEM_COLUMNHEADER        
    ROLE_SYSTEM_COMBOBOX            
    ROLE_SYSTEM_CURSOR              
    ROLE_SYSTEM_DIAGRAM             
    ROLE_SYSTEM_DIAL 				
    ROLE_SYSTEM_DIALOG 				
    ROLE_SYSTEM_DOCUMENT 			
    ROLE_SYSTEM_DROPLIST 			
    ROLE_SYSTEM_EQUATION 			
    ROLE_SYSTEM_GRAPHIC 			
    ROLE_SYSTEM_GRIP 				
    ROLE_SYSTEM_GROUPING 			
    ROLE_SYSTEM_HELPBALLOON 		
    ROLE_SYSTEM_HOTKEYFIELD 		
    ROLE_SYSTEM_INDICATOR 			
    ROLE_SYSTEM_LINK 				
    ROLE_SYSTEM_LIST 				
    ROLE_SYSTEM_LISTITEM 			
    ROLE_SYSTEM_MENUBAR 			
    ROLE_SYSTEM_MENUITEM 			
    ROLE_SYSTEM_MENUPOPUP 			
    ROLE_SYSTEM_OUTLINE 			
    ROLE_SYSTEM_OUTLINEITEM 		
    ROLE_SYSTEM_PAGETAB 			
    ROLE_SYSTEM_PAGETABLIST 		
    ROLE_SYSTEM_PANE 				
    ROLE_SYSTEM_PROGRESSBAR 		
    ROLE_SYSTEM_PROPERTYPAGE 		
    ROLE_SYSTEM_PUSHBUTTON 			
    ROLE_SYSTEM_RADIOBUTTON 		
    ROLE_SYSTEM_ROW 				
    ROLE_SYSTEM_ROWHEADER 			
    ROLE_SYSTEM_SCROLLBAR 			
    ROLE_SYSTEM_SEPARATOR 			
    ROLE_SYSTEM_SLIDER 				
    ROLE_SYSTEM_SOUND 				
    ROLE_SYSTEM_SPINBUTTON 			
    ROLE_SYSTEM_STATICTEXT 			
    ROLE_SYSTEM_STATUSBAR 			
    ROLE_SYSTEM_TABLE 				
    ROLE_SYSTEM_TEXT 				
    ROLE_SYSTEM_TITLEBAR 			
    ROLE_SYSTEM_TOOLBAR 			
    ROLE_SYSTEM_TOOLTIP 			
    ROLE_SYSTEM_WHITESPACE 			
    ROLE_SYSTEM_WINDOW 				

    NAVDIR_UP         
    NAVDIR_DOWN       
    NAVDIR_LEFT       
    NAVDIR_RIGHT      
    NAVDIR_NEXT       
    NAVDIR_PREVIOUS   
    NAVDIR_FIRSTCHILD 
    NAVDIR_LASTCHILD  
);

our $VERSION = '0.01';

require XSLoader;
XSLoader::load('Win32::OleAccxs', $VERSION);


## All the constant below are coming from OLEACC.h
use constant {
    OBJID_WINDOW                    => (0x00000000),
    OBJID_SYSMENU                   => (0xFFFFFFFF),
    OBJID_TITLEBAR                  => (0xFFFFFFFE),
    OBJID_MENU                      => (0xFFFFFFFD),
    OBJID_CLIENT                    => (0xFFFFFFFC),
    OBJID_VSCROLL                   => (0xFFFFFFFB),
    OBJID_HSCROLL                   => (0xFFFFFFFA),
    OBJID_SIZEGRIP                  => (0xFFFFFFF9),
    OBJID_CARET                     => (0xFFFFFFF8),
    OBJID_CURSOR                    => (0xFFFFFFF7),
    OBJID_ALERT                     => (0xFFFFFFF6),
    OBJID_SOUND                     => (0xFFFFFFF5),
    OBJID_QUERYCLASSNAMEIDX         => (0xFFFFFFF4),
    OBJID_NATIVEOM                  => (0xFFFFFFF0),
};

use constant {
    SELFLAG_NONE                    => 0,
    SELFLAG_TAKEFOCUS               => 1,
    SELFLAG_TAKESELECTION           => 2,
    SELFLAG_EXTENDSELECTION         => 4,
    SELFLAG_ADDSELECTION            => 8,
    SELFLAG_REMOVESELECTION         => 16
};

use constant {
    STATE_SYSTEM_UNAVAILABLE        => 0x00000001,  #// Disabled
    STATE_SYSTEM_SELECTED           => 0x00000002,
    STATE_SYSTEM_FOCUSED            => 0x00000004,
    STATE_SYSTEM_PRESSED            => 0x00000008,
    STATE_SYSTEM_CHECKED            => 0x00000010,
    STATE_SYSTEM_MIXED              => 0x00000020,  #// 3-state checkbox or toolbar button
    STATE_SYSTEM_INDETERMINATE      => 0x00000020,
    STATE_SYSTEM_READONLY           => 0x00000040,
    STATE_SYSTEM_HOTTRACKED         => 0x00000080,
    STATE_SYSTEM_DEFAULT            => 0x00000100,
    STATE_SYSTEM_EXPANDED           => 0x00000200,
    STATE_SYSTEM_COLLAPSED          => 0x00000400,
    STATE_SYSTEM_BUSY               => 0x00000800,
    STATE_SYSTEM_FLOATING           => 0x00001000,  #// Children "owned" not "contained" by parent
    STATE_SYSTEM_MARQUEED           => 0x00002000,
    STATE_SYSTEM_ANIMATED           => 0x00004000,
    STATE_SYSTEM_INVISIBLE          => 0x00008000,
    STATE_SYSTEM_OFFSCREEN          => 0x00010000,
    STATE_SYSTEM_SIZEABLE           => 0x00020000,
    STATE_SYSTEM_MOVEABLE           => 0x00040000,
    STATE_SYSTEM_SELFVOICING        => 0x00080000,
    STATE_SYSTEM_FOCUSABLE          => 0x00100000,
    STATE_SYSTEM_SELECTABLE         => 0x00200000,
    STATE_SYSTEM_LINKED             => 0x00400000,
    STATE_SYSTEM_TRAVERSED          => 0x00800000,
    STATE_SYSTEM_MULTISELECTABLE    => 0x01000000,  #// Supports multiple selection
    STATE_SYSTEM_EXTSELECTABLE      => 0x02000000,  #// Supports extended selection
    STATE_SYSTEM_ALERT_LOW          => 0x04000000,  #// This information is of low priority
    STATE_SYSTEM_ALERT_MEDIUM       => 0x08000000,  #// This information is of medium priority
    STATE_SYSTEM_ALERT_HIGH         => 0x10000000,  #// This information is of high priority
    STATE_SYSTEM_PROTECTED          => 0x20000000,  #// access to this is restricted
    STATE_SYSTEM_VALID              => 0x3FFFFFFF,
};

use constant {
    ROLE_SYSTEM_ALERT               => 8,
    ROLE_SYSTEM_ANIMATION           => 54,
    ROLE_SYSTEM_APPLICATION         => 14,
    ROLE_SYSTEM_BORDER              => 19,
    ROLE_SYSTEM_BUTTONDROPDOWN      => 56,
    ROLE_SYSTEM_BUTTONDROPDOWNGRID  => 58,
    ROLE_SYSTEM_BUTTONMENU          => 57,
    ROLE_SYSTEM_CARET               => 7,
    ROLE_SYSTEM_CELL                => 29,
    ROLE_SYSTEM_CHARACTER           => 32,
    ROLE_SYSTEM_CHART               => 17,
    ROLE_SYSTEM_CHECKBUTTON         => 44,
    ROLE_SYSTEM_CLIENT              => 10,
    ROLE_SYSTEM_CLOCK               => 61,
    ROLE_SYSTEM_COLUMN              => 27,
    ROLE_SYSTEM_COLUMNHEADER        => 25,
    ROLE_SYSTEM_COMBOBOX            => 46,
    ROLE_SYSTEM_CURSOR              => 6,
    ROLE_SYSTEM_DIAGRAM             => 53,
    ROLE_SYSTEM_DIAL 				=> 49,
    ROLE_SYSTEM_DIALOG 				=> 18,
    ROLE_SYSTEM_DOCUMENT 			=> 15,
    ROLE_SYSTEM_DROPLIST 			=> 47,
    ROLE_SYSTEM_EQUATION 			=> 55,
    ROLE_SYSTEM_GRAPHIC 			=> 40,
    ROLE_SYSTEM_GRIP 				=> 4,
    ROLE_SYSTEM_GROUPING 			=> 20,
    ROLE_SYSTEM_HELPBALLOON 		=> 31,
    ROLE_SYSTEM_HOTKEYFIELD 		=> 50,
    ROLE_SYSTEM_INDICATOR 			=> 39,
    ROLE_SYSTEM_LINK 				=> 30,
    ROLE_SYSTEM_LIST 				=> 33,
    ROLE_SYSTEM_LISTITEM 			=> 34,
    ROLE_SYSTEM_MENUBAR 			=> 2,
    ROLE_SYSTEM_MENUITEM 			=> 12,
    ROLE_SYSTEM_MENUPOPUP 			=> 11,
    ROLE_SYSTEM_OUTLINE 			=> 35,
    ROLE_SYSTEM_OUTLINEITEM 		=> 36,
    ROLE_SYSTEM_PAGETAB 			=> 37,
    ROLE_SYSTEM_PAGETABLIST 		=> 60,
    ROLE_SYSTEM_PANE 				=> 16,
    ROLE_SYSTEM_PROGRESSBAR 		=> 48,
    ROLE_SYSTEM_PROPERTYPAGE 		=> 38,
    ROLE_SYSTEM_PUSHBUTTON 			=> 43,
    ROLE_SYSTEM_RADIOBUTTON 		=> 45,
    ROLE_SYSTEM_ROW 				=> 28,
    ROLE_SYSTEM_ROWHEADER 			=> 26,
    ROLE_SYSTEM_SCROLLBAR 			=> 3,
    ROLE_SYSTEM_SEPARATOR 			=> 21,
    ROLE_SYSTEM_SLIDER 				=> 51,
    ROLE_SYSTEM_SOUND 				=> 5,
    ROLE_SYSTEM_SPINBUTTON 			=> 52,
    ROLE_SYSTEM_STATICTEXT 			=> 41,
    ROLE_SYSTEM_STATUSBAR 			=> 23,
    ROLE_SYSTEM_TABLE 				=> 24,
    ROLE_SYSTEM_TEXT 				=> 42,
    ROLE_SYSTEM_TITLEBAR 			=> 1,
    ROLE_SYSTEM_TOOLBAR 			=> 22,
    ROLE_SYSTEM_TOOLTIP 			=> 13,
    ROLE_SYSTEM_WHITESPACE 			=> 59,
    ROLE_SYSTEM_WINDOW 				=> 9,
};

use constant {
    NAVDIR_UP           => 1,
    NAVDIR_DOWN         => 2,
    NAVDIR_LEFT         => 3,
    NAVDIR_RIGHT        => 4,
    NAVDIR_NEXT         => 5,
    NAVDIR_PREVIOUS     => 6,
    NAVDIR_FIRSTCHILD   => 7,
    NAVDIR_LASTCHILD    => 8, 
};

## State Array
my @state_system = qw(
    UNAVAILABLE        
    SELECTED           
    FOCUSED            
    PRESSED            
    CHECKED            
    MIXED              
    READONLY           
    HOTTRACKED         
    DEFAULT            
    EXPANDED           
    COLLAPSED          
    BUSY               
    FLOATING           
    MARQUEED           
    ANIMATED           
    INVISIBLE          
    OFFSCREEN          
    SIZEABLE           
    MOVEABLE           
    SELFVOICING        
    FOCUSABLE          
    SELECTABLE         
    LINKED             
    TRAVERSED          
    MULTISELECTABLE    
    EXTSELECTABLE      
    ALERT_LOW          
    ALERT_MEDIUM       
    ALERT_HIGH         
    PROTECTED          
);

# Create a Array to translate the role ID into TEXT
my @role_system = qw(
    NULL
    TITLEBAR
    MENUBAR
    SCROLLBAR
    GRIP
    SOUND
    CURSOR
    CARET
    ALERT
    WINDOW
    CLIENT
    MENUPOPUP
    MENUITEM
    TOOLTIP
    APPLICATION
    DOCUMENT
    PANE
    CHART
    DIALOG
    BORDER
    GROUPING
    SEPARATOR
    TOOLBAR
    STATUSBAR
    TABLE
    COLUMNHEADER
    ROWHEADER
    COLUMN
    ROW
    CELL
    LINK
    HELPBALLOON
    CHARACTER
    LIST
    LISTITEM
    OUTLINE
    OUTLINEITEM
    PAGETAB
    PROPERTYPAGE
    INDICATOR
    GRAPHIC
    STATICTEXT
    TEXT
    PUSHBUTTON
    CHECKBUTTON
    RADIOBUTTON
    COMBOBOX
    DROPLIST
    PROGRESSBAR
    DIAL
    HOTKEYFIELD
    SLIDER
    SPINBUTTON
    DIAGRAM
    ANIMATION
    EQUATION
    BUTTONDROPDOWN
    BUTTONMENU
    BUTTONDROPDOWNGRID
    WHITESPACE
    PAGETABLIST
    CLOCK
);

# Preloaded methods go here.
sub _AccessibleObject 
{
    my $self = {};
    
    $self->{IAccessible} = shift || return undef;
    $self->{id} = shift || 0;

    # Do add reference
    if (shift || $self->{id} != 0) {
        AddRefXs($self->{IAccessible});
    }

    bless $self, __PACKAGE__;
    return $self;
}


sub AccessibleObjectFromPoint 
{
    my ($x, $y) = @_;

    my ($pAcc, $childId) = AccessibleObjectFromPointXs($x, $y);
    return undef unless $pAcc;

    return _AccessibleObject($pAcc, $childId);
}

sub AccessibleObjectFromWindow 
{
    carp "AccessibleObjectFromWindow\n";

    my $hwnd = shift || 0x010010;  # Desktop Window
    my $objid = shift || OBJID_CLIENT;

    my $pAcc = AccessibleObjectFromWindowXs($hwnd, $objid);
    return undef unless $pAcc;

    return _AccessibleObject($pAcc, 0);
}


sub ChildCount 
{
    my $self = shift || return undef;

    return 0 unless ($self->{id} == 0);

    return AccChildCountXs($self->{IAccessible});
}

sub HitTest($$$) 
{
    my $self = shift || return undef;
    
    my ($pAcc, $id) = AccHitTestXs($self->{IAccessible}, shift, shift);

    return undef if(!defined $pAcc);

    # We need to add reference even the self was returned
    return _AccessibleObject($pAcc, $id, $id==0);
}

sub Location 
{
    my $self = shift || return undef;
    
    return AccLocationXs($self->{IAccessible}, $self->{id});
}

sub Select 
{
    my $self = shift || return undef;
    
    return AccSelectXs($self->{IAccessible}, $self->{id}, shift);
}

#sub Child {
#    my $self = shift || croad qq(Didn't specify the object!);
#    
#    return undef if ($self->{id});
#
#    my $child = shift;
#    return $self unless $child;
#
#    my $pAcc = AccChildXs($self->{IAccessible}, $child);
#    return _AccessibleObject($pAcc, 0);
#}

sub Description 
{
    my $self = shift || return undef;
    
    return AccDescriptionXs($self->{IAccessible}, $self->{id});
}

sub Name 
{
    my $self = shift || return undef;
    
    return AccNameXs($self->{IAccessible}, $self->{id});
}

sub Role 
{
    my $self = shift || return undef;

    return AccRoleXs($self->{IAccessible}, $self->{id});
}

sub GetRoleText {
    my $role = shift;

    if (ref($role)) {
        $role = $role->Role;
    }

    return $role_system[$role] if ($role < scalar @role_system);
    return "UNKNOWN";
}

sub State 
{
    my $self = shift || return undef;

    return AccStateXs($self->{IAccessible}, $self->{id});
}

sub GetStateText
{
    my $stat = shift || return "";
    if (ref $stat) {
        $stat = $stat->State
    }

    $stat = int($stat);

    my @rtn;
    my $i = 0;
    
    for (@state_system) {
        if (($stat >> ($i++)) & 0x01) {
            push @rtn, $_;
        }
    }
    
    return @rtn;
}

sub IsState 
{
    my $stat = shift || return 0;
    if (ref $stat) {
        $stat = $stat->State
    }

    $stat = int($stat);

    return 0 if $stat > STATE_SYSTEM_VALID;

    my $rtn = 1; 
    while (shift) {
        $rtn = (($stat & $_) == $_);
        last unless $rtn;
    }

    return $rtn;
}

sub Value 
{
    my $self = shift || return undef;

    return AccValueXs($self->{IAccessible}, $self->{id});
}

sub Children 
{
    my $self = shift || return undef;

    # simple element doesn't have any child
    return (undef) if ($self->{id} != 0);

    # Get the Hash of children IAccessible=>ChildId

    my @children = AccessibleChildrenXs($self->{IAccessible});
    
    #print "Child count: ", scalar @children, "\n";
    #print Dumper(@children);

    my @rtn;
    while (scalar @children) {
        # Create new Object
        my $child = _AccessibleObject(shift @children, shift @children);
        if ($child) {
            push @rtn, $child;
        }
    }

    return @rtn;
}

sub Selection 
{
    my $self = shift || return undef;

    # simple element should test itself
    if ($self->{id} != 0) {
        return ($self) if ($self->State() | STATE_SYSTEM_SELECTED);
        return (undef);
    }

    # Get the Hash of children IAccessible=>ChildId
    my @children = AccSelectionXs($self->{IAccessible});
    
    my @rtn;
    while (scalar @children) {
        # Create new Object
        push @rtn, _AccessibleObject(shift @children, shift @children);
    }

    return @rtn;
}

sub Parent 
{
    my $self = shift || return undef;

    return _AccessibleObject($self->{IAccessible}, 0, 1) if($self->{id} != 0);
    
    my $parent = AccParentXs($self->{IAccessible});
    return undef unless $parent;
    return _AccessibleObject($parent, 0);
}

sub Navigate($$)
{
    my $self = shift || return undef;

    my ($acc, $id) = AccNavigateXs($self->{IAccessible}, $self->{id}, shift);
    if ($acc) {
        return _AccessibleObject($acc, $id, 1)
    }
    return undef;
}

sub GetWindowHwnd
{
    my $self = shift || return undef;

    return WindowFromAccessibleObjectXs($self->{IAccessible});
}

sub AllData
{
    my $self = shift || return undef;
    
    my $rtn = {};
    $rtn->{'Children'}      = $self->ChildCount();
    $rtn->{'Name'}          = $self->Name();
    $rtn->{'Location'}      = ($self->Location());
    $rtn->{'State'}         = $self->State();
    $rtn->{'Role'}          = $self->Role();
    $rtn->{'Value'}         = $self->Value();
    $rtn->{'Description'}   = $self->Description();

    return $rtn;
}

sub DESTROY 
{
    my $self = shift;

    ReleaseXs($self->{IAccessible}) if $self->{IAccessible};
}


1;
__END__
# Below is stub documentation for your module. You'd better edit it!

=head1 NAME

Win32::OleAccxs - Perl extension for to utilize the Microsoft IAccessible interface

=head1 SYNOPSIS

  use Win32::OleAccxs;
  
  my $wnd = AccessibleObjectFromWindow(0x010010, OBJID_CLIENT);
  print $wnd, " it is the Desktop object"

  # find out all children
  Win32::OleAccxs::ChildCount($wnd);
  $wnd->ChildCount();  

=head1 DESCRIPTION

It provides the way to utilize IAccessible interface to traval the Windows objects content. Especially the MS grid control which can't be retreived by Win32::GuiTest

=head2 EXPORT

None by default.


=head1 SEE ALSO

Win32::GuiTest

=head1 AUTHOR

A. U. Thor, E<lt>a.u.thor@a.galaxy.far.far.awayE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 by Yi Zhao

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.1 or,
at your option, any later version of Perl 5 you may have available.


=cut
